﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataPreparation;

namespace TMSimulator
{
    class FingerprintDistance : IDistance
    {
        public int Length { get; set; } // indicates the length of the partial text that would be hashed
        public int Overlap { get; set; }  // indicates the overlap length used in the fingerprints algorithm
        const int DEFAULT_LENGTH = 1; // for the fingerPrints genartor
        const int DEFAULT_OVERLAP = 1; // for the fingerPrints genartor

        FingerprintDB fingerprintDB;


        public FingerprintDistance()
        {
            fingerprintDB = FingerprintDB.getInstance(); // for a singelton class- will be like a global field that contain all fp
            Length = DEFAULT_LENGTH;
            Overlap = DEFAULT_OVERLAP;
        }

        /// <summary>
        /// return the accumulative tmscore
        /// </summary>
        /// <param name="name"></param>
        /// <param name="user"></param>
        /// <param name="gradedDB"></param>
        /// <returns></returns>
        public double getTMScore(string name, User user, DataPreparation.GradedDB gradedDB)
        {
            IItem temp = null;
            FileAccess item = null;
            Dictionary<FileAccess, Dictionary<int, int>> Documents = new Dictionary<FileAccess, Dictionary<int, int>>(); //temp field

            if (user.History == null) // if list doesnt initialize
                return 0;

            if (!(fingerprintDB.FP.ContainsKey(name))) // if the global fp does not contain the fp of the document
            {
                temp = gradedDB.items[name];
                if (temp == null)
                    return 0;
                else
                    GenerateFingerprints(temp.getName(), temp.GetText()); // add it
            }


            if (user.History.Count == 0)   // if this the first doc of the user, the tmscore will be the tmscore of the doc,no need for the alghoritm
                return gradedDB.items[name].getTMScore();

            else // there is at least 2 docs for the user
                item = new FileAccess(name);
            Documents.Add(item, fingerprintDB.FP[name]);
            foreach (FileAccess f in user.History.Keys)  // insert into the temp field the all docs of the user
            {
                item = new FileAccess(f.FilePath);
                Documents.Add(item, fingerprintDB.FP[f.FilePath]);
            }
            // sort the docs by the count of the pf
            Documents = (from entry in Documents orderby entry.Value.Count descending select entry).ToDictionary(pair => pair.Key, pair => pair.Value);
            double tmscore = gradedDB.items[Documents.ElementAt(0).Key.FilePath].getTMScore();

            HashSet<int> totalFP = new HashSet<int>(Documents.ElementAt(0).Value.Keys);
            HashSet<int> currentDocFP;

            for (int i = 1; i < Documents.Count; i++)
            {
                double t = gradedDB.items[Documents.ElementAt(i).Key.FilePath].getTMScore();
                currentDocFP = new HashSet<int>(Documents.ElementAt(i).Value.Keys);
                tmscore += (double)(1 - Overlaps(totalFP, currentDocFP)) * t;
                totalFP.UnionWith(currentDocFP);
            }

            return Math.Round(tmscore, 5, MidpointRounding.AwayFromZero);


        }

        /// <summary>
        /// Checking the level of overlap between the documents
        /// </summary>
        /// <param name="longSet"></param>
        /// <param name="shortSet"></param>
        /// <returns></returns>
        public double Overlaps(HashSet<int> longSet, HashSet<int> shortSet)
        {
            int count = 0;

            foreach (int fingerprint in shortSet)
            {
                if (longSet.Contains(fingerprint))
                    count++;
            }

            return (double)count / (double)shortSet.Count;
        }

        /// <summary>
        /// Recieves an entire text and returns its fingerprints as a dictionary,
        /// with the hashed value as the key and its total appearences being the value.
        /// </summary>
        public void GenerateFingerprints(string name, string text)
        {
            List<string> entireText = text.Split(new char[] { ' ' }).ToList();
            Dictionary<int, int> toReturn = new Dictionary<int, int>();
            string txt = "";  // indicates a part of the entire text
            int hashValue;

            for (int i = 0; i <= entireText.Count - Length; i++)
            {

                for (int j = 0; j < Length; j++)
                {
                    txt += entireText[i + j] + " ";
                }

                txt = txt.Remove(txt.Length - 1);  // deleting the extra space from the end of the string
                hashValue = txt.GetHashCode();   // computing the hash code of the string

                if (toReturn.ContainsKey(hashValue))   // adding the fingerprint
                    toReturn[hashValue] += 1;
                else
                    toReturn.Add(hashValue, 1);

                txt = "";
                i += Overlap;
            }
            // sorting the fingerprints by their appearence in the document
            toReturn = (from entry in toReturn orderby entry.Value descending select entry).ToDictionary(pair => pair.Key, pair => pair.Value);
            fingerprintDB.FP.Add(name, toReturn);
        }
    }
}



